#include "httpLib.h"

HttpLib::HttpLib()
{
    _ios = new boost::asio::io_service;
    _socket = new boost::asio::ip::tcp::socket(*_ios);
    _response = NULL;
}

HttpLib::~HttpLib()
{
    this->close();
    if (this->_response)
        delete this->_response;
    delete this->_socket;
    delete this->_ios;
}

bool                                                HttpLib::httpConnection(const std::string& addr, const std::string& port)
{
    if (!this->_host.empty())
        return (false);
    try
    {
        std::string                                 ip = this->getHostByName(addr, port).front();
        boost::asio::ip::tcp::endpoint              endpoint(boost::asio::ip::address::from_string(ip), atoi(port.c_str()));
        this->_socket->connect(endpoint);
        this->_host = addr;
    }
    catch(boost::system::system_error& e)
    {
        HttpLib::close();
        return (false);
    }
    return (true);
}

bool                                            HttpLib::request(const std::string& method, const std::string& url, std::map<std::string, std::string>* args)
{
    if (this->_host.empty())
        return (false);
    try
    {
        std::string                             request;
        if (method == "GET")
        {
            request = "GET " + url + " HTTP/1.1" + ENDL;
            request = request + "Host: " + this->_host + ENDL;
            if (!this->_auth64.empty())
                request += "Authorization: Basic " + this->_auth64 + ENDL;
            request += ENDL;
        }
        else if (method == "POST")
        {
            std::string param = args ? HttpLib::urlEncode(*args) : "";
            request = "POST " + url + " HTTP/1.1" + ENDL;
            request = request + "Host: " + this->_host + ENDL;
            if (!this->_auth64.empty())
                request += "Authorization: Basic " + this->_auth64 + ENDL;
            request = request + "Content-Type: application/x-www-form-urlencoded" + ENDL;
            request = request + "Content-Length: ";
            std::ostringstream oss;
            oss << param.length();
            request = request + oss.str() + ENDL;
            request += ENDL;
            request = request + param + ENDL;
            request += ENDL;
        }
        else
            return (false);
        HttpLib::send(request);
        HttpLib::receive();
        return (true);
    }
    catch (boost::system::system_error& e)
    {
        HttpLib::close();
        return (false);
    }
}

HttpResponse*                                   HttpLib::getRequest() const
{
    return (this->_response);
}

void                                            HttpLib::send(const std::string& request)
{
    boost::system::error_code                   error;
    this->_socket->send(boost::asio::buffer(request), 0, error);
    if (error)
        throw boost::system::system_error(error);
}
void                                            HttpLib::receive(void)
{
    boost::asio::streambuf                      content;
    boost::system::error_code                   error;
    while (!error)
    {
        boost::asio::read(*this->_socket, content, boost::asio::transfer_at_least(1), error);
    }
    if (error != boost::asio::error::eof)
        throw boost::system::system_error(error);
    if (this->_response)
        delete this->_response;
    this->_response = new HttpResponse(boost::asio::buffer_cast<const char*>(content.data()));
}

const std::list<std::string>                    HttpLib::getHostByName(const std::string& name, const std::string& port) const
{
    std::list<std::string>                      ipList;

	boost::asio::ip::tcp::resolver resolver(*this->_ios);
	boost::asio::ip::tcp::resolver::query query(name, port);
	boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query);
	boost::asio::ip::tcp::resolver::iterator ite;
	while (it != ite)
	{
		boost::asio::ip::tcp::endpoint endpoint = *it;
		ipList.push_back(endpoint.address().to_string());
        it++;
	}
    return (ipList);
}

const std::string                               HttpLib::urlEncode(const std::map<std::string, std::string>& args)
{
    std::string                                 urlEncode;

    std::map<std::string, std::string>::const_iterator    it = args.begin();
    std::map<std::string, std::string>::const_iterator    ite = args.end();
    while (it != ite)
    {
        urlEncode += it->first + "=" + it->second;
        it++;
        if (it != ite)
            urlEncode += "&";
    }
    return (urlEncode);
}

bool                                            HttpLib::setAuth(const std::string& logPass)
{
    size_t                                      sepPos = logPass.find(':');

    if (sepPos != std::string::npos)
    {
        this->_login = logPass.substr(0, sepPos);
        this->_pass = logPass.substr(sepPos + 1, logPass.length() - sepPos - 1);
        this->_auth64 = this->Base64_encode(logPass);
        return (true);
    }
    return (false);
}

void                                            HttpLib::close()
{
    this->_socket->close();
    this->_host.clear();
    this->_login.clear();
    this->_pass.clear();
    this->_auth64.clear();
}

const std::string                                   HttpLib::Base64_encode(const std::string& data)
{
    typedef union
    {
        uint32_t    l;
        char        c[4];
    }               un32;
    std::string b64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    uint32_t mask1 = 0xFC000000;
    uint32_t mask2 = 0x03F00000;
    uint32_t mask3 = 0x000FC000;
    uint32_t mask4 = 0x00003F00;
    char fillchar = '=';
    size_t trail = data.size()%3;
    size_t sz   = data.size()/3*4;
    sz += (trail != 0) ? 4 : 0;

    un32 b64;
    std::string out;
    out.resize(sz);
    size_t i = 0, k = 0;

    while(i < data.size()-trail)
    {
        b64.c[3] = data[i++];
        b64.c[2] = data[i++];
        b64.c[1] = data[i++];

        out[k++] = b64table[static_cast <int> ((b64.l & mask1) >> 26)];
        out[k++] = b64table[static_cast <int> ((b64.l & mask2) >> 20)];
        out[k++] = b64table[static_cast <int> ((b64.l & mask3) >> 14)];
        out[k++] = b64table[static_cast <int> ((b64.l & mask4) >>  8)];
    }
    b64.l = 0;

    switch(trail)
    {
        case 1:
            b64.c[3] = data[i++];
            out[k++] = b64table[static_cast <int> ((b64.l & mask1) >> 26)];
            out[k++] = b64table[static_cast <int> ((b64.l & mask2) >> 20)];
            out[k++] = fillchar;
            out[k++] = fillchar;
            break;
        case 2:
            b64.c[3] = data[i++];
            b64.c[2] = data[i++];
            out[k++] = b64table[static_cast <int> ((b64.l & mask1) >> 26)];
            out[k++] = b64table[static_cast <int> ((b64.l & mask2) >> 20)];
            out[k++] = b64table[static_cast <int> ((b64.l & mask3) >> 14)];
            out[k++] = fillchar;
        break;
    }
    return out;
}
